import {join} from 'path';
import {writeFileSync, readFileSync} from 'fs';
import {sync as glob} from 'glob';
import {red} from 'chalk';
import {BuildPackage} from './build-package';
import {tsCompile} from './ts-compile';

/** Incrementing ID counter. */
let nextId = 0;

/** Compiles the TypeScript sources of a primary or secondary entry point. */
export async function compileEntryPoint(buildPackage: BuildPackage, tsconfigName: string,
                                        secondaryEntryPoint = '', es5OutputPath?: string) {
  const entryPointPath = join(buildPackage.sourceDir, secondaryEntryPoint);
  const entryPointTsconfigPath = join(entryPointPath, tsconfigName);
  const ngcFlags = ['-p', entryPointTsconfigPath];

  if (es5OutputPath) {
    ngcFlags.push('--outDir', es5OutputPath, '--target', 'ES5');
  }

  return tsCompile('ngc', ngcFlags).catch(() => {
    const error = red(`Failed to compile ${secondaryEntryPoint} using ${entryPointTsconfigPath}`);
    console.error(error);
    return Promise.reject(error);
  });
}

/**
 * Adds `importAs` property to all generated metadata.json files for a package's secondary
 * entry-points. This is necessary for unit tests so that the imports generated by using the
 * `providedIn` syntax are able to resolve symbols to the "expected" location (e.g.
 * "@angular/cdk/overlay" instead of a local relative path). Assumes that the package has already
 * been built.
 */
export function addImportAsToAllMetadata(buildPackage: BuildPackage) {
  for (const entryPoint of buildPackage.secondaryEntryPoints) {
    addImportAs(buildPackage.name, buildPackage.outputDir, entryPoint);
  }
}

/** Renames `ɵa`-style re-exports generated by Angular to be unique across compilation units. */
export function renamePrivateReExportsToBeUnique(buildPackage: BuildPackage,
                                                  secondaryEntryPoint = '') {
  // When we compiled the typescript sources with ngc, we do entry-point individually.
  // If the root-level module re-exports multiple of these entry-points, the private-export
  // identifiers (e.g., `ɵa`) generated by ngc will collide. We work around this by suffixing
  // each of these identifiers with an ID specific to this entry point. We make this
  // replacement in the js, d.ts, and metadata output.
  if (buildPackage.exportsSecondaryEntryPointsAtRoot && secondaryEntryPoint) {
    const entryPointId = nextId++;
    const outputPath = join(buildPackage.outputDir, secondaryEntryPoint);
    const esm5OutputPath = join(buildPackage.esm5OutputDir, secondaryEntryPoint);

    addIdToGlob(outputPath, entryPointId);
    addIdToGlob(esm5OutputPath, entryPointId);
  }
}

/** Adds `importAs` property to generated all metadata.json files for an entry-point. */
function addImportAs(packageName: string, outputPath: string, secondaryEntryPoint: string): void {
  const path = join(outputPath, secondaryEntryPoint);
  glob(join(path, '**/*.+(metadata.json)')).forEach(metadataPath => {
    let metadata = JSON.parse(readFileSync(metadataPath, 'utf-8'));
    metadata[0]['importAs'] = `@angular/${packageName}/${secondaryEntryPoint}`;
    writeFileSync(metadataPath, JSON.stringify(metadata), 'utf-8');
  });
}

/** Updates exports in designated folder with identifier specified. */
function addIdToGlob(outputPath: string, entryPointId: number): void {
  glob(join(outputPath, '**/*.+(js|d.ts|metadata.json)')).forEach(filePath => {
    let fileContent = readFileSync(filePath, 'utf-8');
    // We check for double ɵ to avoid mangling symbols like `ɵɵdefineInjectable`.
    fileContent = fileContent.replace(/ɵ(ɵ)?[a-z]+/g,
        (match, isDoubleTheta) => {
          return isDoubleTheta ? match : match + entryPointId;
        });
    writeFileSync(filePath, fileContent, 'utf-8');
  });
}

